Skip to content

docs(spec): ApproverType.describe() qualifies manager instead of offering it as a bare allowed value - #17640

Merged
os-bill merged 2 commits into
mainfrom
claude/issue-17579-approver-type-manager-describe
Sep 11, 2026
Merged

os-bill merged 2 commits into
mainfrom
claude/issue-17579-approver-type-manager-describe

Conversation

@os-bill

@os-bill os-bill commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Part of #17579

  • Clause-②: no — this PR puts no new key on any published payload. ApproverType's accept set is byte-identical (no member added, removed or renamed), check:api-surface is green on the rebuilt dist/*.d.ts, and check:authorable-surface left the checked-in artifacts untouched. The only new bytes are prose.

What this changes

ApproverType carried no .describe() at all, so the generated reference page rendered ## ApproverType with nothing but an ### Allowed Values list. manager — the one rung an author cannot operate on a stock install — read exactly like the nine members that work.

  • packages/spec/src/automation/approval.zod.ts — the enum gains a .describe() that qualifies manager and points at the remedy instead of restating it.
  • content/docs/references/automation/approval.mdxregenerated, never hand-edited (see the generator evidence below).
  • packages/lint/src/validate-approval-approvers.ts — one paragraph added to the DEPENDENCY docblock above MANAGER_ONLY_REMEDY, naming the new describe among the lines that go stale if manager_id ever gains a write surface. The triage comment called this out as the part a round is most likely to skip; it is a comment only.

The describe, verbatim:

Approval step approver type. manager is a directory-sync dependency rather than something an author configures here: it resolves the submitter's sys_user.manager_id at runtime, and that column has no product write surface, so until an operator populates it from outside the product a manager step resolves to nobody and the request waits. os lint reports that at authoring time as approval-approvers-may-resolve-empty and carries the graded population routes and the full remedy; the Approvals guide states the same remedy in prose.

Route (iii), the triage default: it points, it does not restate. MANAGER_ONLY_REMEDY / MANAGER_ONLY_ROUTES stay the single authoritative copy of the 667-character remedy. No third copy was written, so there is nothing new to keep in step — a pointer cannot drift into disagreement with what it points at. ⛔ No export was added to packages/spec (route (ii) was fenced).

Prerequisite readings — taken by state on origin/main @ ea2940d1c4, not inherited from the card

1. The manager describe. ⚠️ Correction to the card: ApproverType had no .describe() to read unqualified — it had none. Its JSDoc (approval.zod.ts:24-30) renders nowhere: renderSchemaSection (packages/spec/scripts/lib/schema-section.ts:328-330) prints mainDef.description, which only .describe() / .meta({description}) fills. Positive control that the mechanism is real: HttpMethod's .describe() (packages/spec/src/shared/http.zod.ts:45) renders at content/docs/references/shared/http.mdx:46. The card's substance stands — the page sold manager unqualified — but the fix is a describe added, not a describe edited.

2. The generated page. content/docs/references/automation/approval.mdx:113 is ## ApproverType, :115 is ### Allowed Values, :117 is the manager bullet — the exact window the card names. Its banner (:6) still reads ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. and the source callout (:9-10) still names packages/spec/src/automation/approval.zod.ts. Both hold.

3. ⭐ sys_user.manager_id has no product write surface — re-measured, with the negative leg lit.

A throwaway probe drove the identity write guard's real beforeUpdate handler in a user context, then was deleted (working tree verified clean afterwards); both legs went through the same handler:

PROBE legA surviving payload = {"id":"u1","name":"Keep"}      # manager_id stripped
PROBE legB surviving payload = {"id":"u1","locale":"zh-CN"}   # control survives intact
PROBE whitelist = ["name","image","locale"]
Test Files  2 passed (2)   Tests  29 passed (29)
  • Leg A (subject). { id, name, manager_id } came back as { id, name }manager_id never reaches the row. { id, manager_id } alone is refused loudly: PERMISSION_DENIED / 403.
  • Leg B (negative leg). locale — a field that IS writable — passed the same probe unchanged. ⇒ the instrument is not a refusal that refuses everything.
  • The landed pins the card cites are still exactly where it says: identity-write-guard.test.ts:132 (/Editable fields: name, image, locale/) and :186 (getManagedUpdateWhitelist('sys_user') equals new Set(['name','image','locale'])). Both green in the same run.

readonly: true on the column, with a discriminating control — readonly occurrences inside each field block of packages/platform-objects/src/identity/sys-user.object.ts:

field readonly occurrences
manager_id 1
primary_business_unit_id 1
locale (control — writable) 0
name (control — writable) 0

No accepting route: across the 59 non-test packages/plugins/plugin-auth/src/*.ts files (enumerated with git ls-tree, not by grepping contents for a filename), manager_id occurs 5 times against a firing control of phone_number at 28 — and all 5 sit in managed-extension-fields.ts and sys-user-writable-fields.ts, both of which name the column only to record that it is not writable (MANAGED_EXTENSION_EDITABLE_FIELDS.sys_user is new Set(['locale'])). Counts are grep -o | wc -l, not grep -c.

The premise holds. premise_still_valid: true.

4. What PR #17575 already fixed — not redone. The manager callout in content/docs/automation/approvals.mdx:65-87 and the qualification in content/docs/capabilities/approvals.mdx:14-19 are both present and untouched by this PR (git diff names neither file).

The page was regenerated, not hand-edited

$ pnpm --filter @objectstack/spec build           # required first (the dist caveat)
$ pnpm --filter @objectstack/spec check:generated
  ✗ check:docs   content/docs/references/**       # 1 of 15 stale — exactly the one predicted
$ pnpm --filter @objectstack/spec gen:docs
  ✅ Generated 222 files
$ git status --porcelain
   M content/docs/references/automation/approval.mdx     # 1 of the 222 changed
$ pnpm --filter @objectstack/spec check:generated
  ✓ All 15 generated artifacts are up to date.

The generator rewrote all 222 reference pages and exactly one moved. Beyond the ## ApproverType section, the describe also reached the previously blank Description cells of the type rows in ApprovalNodeApprover and ApprovalNodeConfig.approvers — measured on the regenerated output, not predicted from the zod registry semantics (which read the other way).

Verification

  • pnpm --filter @objectstack/spec check:generatedall 15 green, including check:api-surface, check:authorable-surface, check:docs.
  • Derived gate familynode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack over this diff: 100 families derived, 98 run green, 0 unrun, 2 NOT MEASURED. Reconciled with --ran carrying each recorded exit code.
    • pnpm check:dual-build-cjs-loads and pnpm check:lean-entry-closure both exit 3 — PREREQUISITE NOT MET (they load built entry points repo-wide and this worktree has no full pnpm build). ⛔ Recorded as NOT MEASURED, not as green; CI builds and runs both.
    • One run was discarded and re-run rather than silently retried: check:skill-examples first exited 1 for want of packages/client-react/dist, a prerequisite refusal, not a red. After pnpm --filter @objectstack/client-react build it is green — ✅ 258 prose examples type-check across 3 surface(s).
    • A second discarded run: pnpm --filter … run test --concurrency=2 forwarded --concurrency into the vitest script and failed on flag parsing. Re-run correctly as pnpm --workspace-concurrency=2 --filter … run test.
  • pnpm --workspace-concurrency=2 --filter @objectstack/spec --filter @objectstack/lint run test — spec 473 files / 13429 tests passed, lint 103 files / 3747 tests passed.
  • pnpm --workspace-concurrency=2 --filter @objectstack/spec --filter @objectstack/lint run typecheck — exit 0, both test-layer debt ledgers held.
  • pnpm lint (eslint . --no-inline-config, the whole repo) — exit 0. No narrowing was needed, so none is claimed.
  • pnpm check:nul-bytes green, plus a direct control-character scan over the four touched files (grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' → no match).

Heavy runs went through scripts/pm/os-verify-lock.sh; the verdicts above are its VERDICT command-exit lines, with each exit code captured before any pipe.

Changeset

.changeset/17579-approver-type-manager-describe.md@objectstack/spec: patch. The describe ships: @objectstack/spec's files[] carries dist, json-schema and src/**/*.zod.ts, and the new string is measured in all three on the built tree — dist/automation/index.js + .mjs (2 files, against a lit control of an existing describe from the same module, also 2), four json-schema/ documents, and the shipped approval.zod.ts source. Prose only, no surface widening ⇒ patch, not minor.

@objectstack/lint is deliberately not graded. It publishes dist only, and the new docblock sentence is absent from it (0 files) while a runtime string from the same source file is present in 4 and a pre-existing comment from the same docblock is absent in 0 — comments are stripped by construction, so nothing published moves there.

Out of scope


Generated by Claude Code

…fering it bare

`ApproverType` carried no `.describe()` at all, so the generated reference page
`content/docs/references/automation/approval.mdx` rendered `## ApproverType`
with nothing but an `### Allowed Values` list: `manager` — the one rung an
author cannot operate on a stock install, because `sys_user.manager_id` has no
product write surface — read exactly like the nine members that work.

The describe qualifies it and POINTS at the remedy rather than restating it:
`MANAGER_ONLY_REMEDY` / `MANAGER_ONLY_ROUTES` in
`packages/lint/src/validate-approval-approvers.ts` stay the single
authoritative copy, and that file's `DEPENDENCY` docblock now names this new
string among the lines that go stale if the column ever gains a write surface.

No enum member is added, removed or renamed.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
`pnpm --filter @objectstack/spec gen:docs` rewrote all 222 reference pages;
exactly one changed. The `## ApproverType` section now carries the qualifying
describe above its `### Allowed Values` list, and the `type` rows of
`ApprovalNodeApprover` and `ApprovalNodeConfig.approvers` — previously blank —
carry it too. The page was never hand-edited.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 1 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/lint/src/validate-approval-approvers.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/approvals.mdx (via ApproverType (symbol, a top-level const object))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via ApproverType (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/validate-approval-approvers.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 135 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 76c9fab30ca406b7b1f06b8ca3db286af9f2bf8bpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 10ff2592b80ac969d9eaf7b0c968c1533c0a5923 — the merge of head 1aca42a2d7599ada6b901c9a56a85f7293b8d757 into base 76c9fab30ca406b7b1f06b8ca3db286af9f2bf8b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 10ff2592b80ac969d9eaf7b0c968c1533c0a5923 && git checkout 10ff2592b80ac969d9eaf7b0c968c1533c0a5923
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 76c9fab30ca406b7b1f06b8ca3db286af9f2bf8b 1aca42a2d7599ada6b901c9a56a85f7293b8d757 && git checkout -B drift-repro 76c9fab30ca406b7b1f06b8ca3db286af9f2bf8b && git merge --no-ff 1aca42a2d7599ada6b901c9a56a85f7293b8d757

node scripts/docs-audit/affected-docs.mjs --json 76c9fab30ca406b7b1f06b8ca3db286af9f2bf8b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 76c9fab30ca406b7b1f06b8ca3db286af9f2bf8b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Sep 11, 2026
@os-bill
os-bill marked this pull request as ready for review September 11, 2026 07:48
@os-bill
os-bill enabled auto-merge September 11, 2026 07:48
@os-bill
os-bill added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 2eb4724 Sep 11, 2026
36 checks passed
@os-bill
os-bill deleted the claude/issue-17579-approver-type-manager-describe branch September 11, 2026 08:17
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…ce, and the `manager` rung stops resolving to nobody (objectstack-ai#17993)

Fixes objectstack-ai#16678

Clause-②: no

Phase 3 of the manager relationship, executed against the director-seat
ruling (comment `5651634638`, decision batch objectstack-ai#127 item 1, option B) on
the Phase 2 design (comment `5648180780`). Maintainer, verbatim — kept
in the original, because rewriting the quotation rewrites the ruling:

> 同意 经理 = 管理员在用户上显式设置的 `manager_id`;部门负责人 = 单元上的 `manager_user_id`,两者独立。

## The hole, reproduced before it was closed

`sys_user.manager_id` drives the approvals `{ type: 'manager' }` rung
and the `own_and_reports` read scope, and **no product surface could
write it**. Measured on the branch point:

| route | reading | control |
| :--- | ---: | :--- |
| `admin-import-users.ts` matches `manager_id` | **0** | `phone_number`
**8**, same file, same scan |
| whole of `plugin-auth/src` writing `manager_id` | **0** | 11 total
matches, every one a test assertion, a comment, or
`managed-extension-fields.ts` listing it as *not* editable |
| ADR-0092 D2 managed-update whitelist for `sys_user` | `{name, image,
locale}` | — |

So on any install without a directory sync the rung expanded to nobody.

## What this delivers

**1. The endpoint.** `POST /api/v1/auth/admin/set-user-manager`, body `{
userId, managerId }`, `managerId: null` clears. An ObjectStack mount on
the raw app ahead of the better-auth catch-all — the `unlock-user` /
`import-users` family — platform-admin gated (ADR-0068), ledgered in
`auth-route-ledger.ts` and added to the conformance pin that asserts the
`source: 'objectstack'` set exactly.

**2. It reaches the column by CONTEXT, never by whitelist.** The handler
runs under a system context, exactly as `admin-import-users` already
reaches `phone_number` and `role`; both write guards gate on
`isUserContextWrite`, spelled `Boolean(userId) && isSystem !== true`. So
`SYS_USER_PROFILE_EDIT_FIELDS`,
`MANAGED_EXTENSION_EDITABLE_FIELDS.sys_user` and `manager_id`'s
`readonly: true` are all **unmoved**, and ADR-0092 D4 holds by
construction. A test asserts the whitelist is still exactly `{name,
image, locale}`, so a future change that "fixes" this by widening Tier 1
goes red here rather than quietly.

**3. Five refusals, every one enforced at the write**, each with its
reason in the code:

| refusal | `error.code` | status | `error.details.reason` |
| :--- | :--- | ---: | :--- |
| body shape | `INVALID_REQUEST` | 400 | `invalid_body` |
| target user missing | `RESOURCE_NOT_FOUND` | 404 | `user_not_found` |
| proposed manager missing | `INVALID_REFERENCE` | 400 |
`manager_not_found` |
| self-assignment | `INVALID_FIELD` | 400 | `self_assignment` |
| cycle | `RESOURCE_CONFLICT` | 409 | `cycle` |
| depth cap | `VALUE_OUT_OF_RANGE` | 400 | `max_depth_exceeded` |
| cross-organization | `INVALID_REFERENCE` | 400 | `cross_organization`
|
| directory-owned identity | `PERMISSION_DENIED` | 403 |
`idp_provisioned` |

The cycle check has to be here: the only manager-chain walkers in the
open tree are single-hop (`ApprovalService.lookupManager`,
`TeamGraphService.managerOf`, one row each) and the multi-hop resolver
ships outside this repo, so nothing downstream catches a loop this
endpoint lets in. The walk carries its own `seen` set, so a loop that
already exists upstream is reported rather than hung on.

The cross-organization refusal is the write-time half of
`managerIsProvablyOutsideOrg`, and deliberately shares its "provably"
shape: `sys_user` carries no `organization_id`, so `sys_member` rows are
the only tenancy fact, and an *absent* fact is not a negative one. Both
halves are wanted — data drifts after a write.

The `idp_provisioned` refusal is the ruling's row 5 (P1, directory wins
per identity), keyed on the `sys_user.source` column the platform
already stamps. It applies to the clear as well as the set, because both
are writes the next sync would revert — the shape ADR-0049 exists to
refuse.

**Why these codes and not dedicated ones:** `error.code` is a closed
vocabulary (`StandardErrorCode` union `ERROR_CODE_LEDGER`) and both live
in `packages/spec`, which this lane is fenced out of. So every code is
one this package may already emit, and `error.details.reason` carries
the machine-readable discrimination, pinned per refusal by the tests. A
dedicated code per refusal is the better shape and is reported as a
follow-up rather than reached over the fence for.

**4. The lint rule keeps its finding; only its remedy changed.**
`approval-approvers-may-resolve-empty` and `stackWiresManagerChain` are
untouched as a rule — the dead end it reports **survives** the write
surface, because a static check still cannot read the column; only its
*cause* became recoverable. `MANAGER_ONLY_REMEDY` /
`MANAGER_ONLY_ROUTES` now name the endpoint, its body, how to clear the
link and what it refuses; the docblock's graded routes are re-taken, and
the two "NOT here" grades (SCIM, bulk import) re-measured unchanged.

**5. The docs callout** in `content/docs/automation/approvals.mdx`
carries the same rewrite in prose, including the directory-precedence
rule.

## STOPPED AT THE `packages/spec` FENCE — exactly one part

`ApproverType.describe()` in
`packages/spec/src/automation/approval.zod.ts` still asserts "*that
column has no product write surface*", and that sentence stops being
true with this PR. Nothing is pushed for it here.

Verified on `origin/main` rather than assumed, as the dispatch required:
commit `2eb472418` (objectstack-ai#17640) **is** an ancestor of HEAD — `git merge-base
--is-ancestor` exit **0**, which is self-certifying and needs no control
leg. But what that commit landed is a `.describe()` that *points* at the
remedy while still carrying the now-false assertion. So the third
dependent is landed and stale, not landed and fixed. The generated
`content/docs/references/automation/approval.mdx` renders the string
verbatim and is auto-generated, so it is downstream of the same fenced
edit.

⇒ For the `domain:spec` seat. It is the only part stopped at the fence.

## Acceptance notes

- **Ruling row 7 (bulk import)** — admitting `manager_id` to the import
tier is ruled but is not in this lane's scope fence.
`admin-import-users.ts` is untouched and its 0-match reading is
re-stated in the lint docblock as still current.
- **Ruling row 4 (org-unit derivation)** is cut by the ruling.
`sys_business_unit.manager_user_id` and `sys_user.manager_id` stay
independent; `BusinessUnitGraphService.headOf` is not consumed.
- **The delegated-admin axis** (design §3.3) is not declared. ADR-0068
platform-admin is the floor; a fourth axis on `delegated-admin-gate.ts`
is a governed permission-model change and ADR-0049 forbids declaring
what is not enforced. Recorded as a follow-up, with the Entra precedent
attached in the design.
- **The Console affordance** is objectui#9361, a different repo. The
design fixes its shape: a read-only lookup in the existing
`Organization` group plus a row action posting this endpoint, never a
field in the standard edit form.
- **Design §8.1 observations, noted and not filed** — this card's own
material rather than new defects, and the ruling assigns them to
whichever change touches the file first: the dead-slot literal reads
`manager:undefined` because `value` is omitted for `manager`; and the
objectstack-ai#3807 "expanded to nobody" pin table in `approval-service.test.ts`
covers `team` / `department` / `position` / `org_membership_level` and
omits `manager`, so that behaviour is real by set membership and not by
test. Neither file is touched here.
- **`content/docs/permissions/tenant-audit-census.mdx`** and its counts
ledger moved 222 to 223 write call sites. That is this PR's own new
`engine.update`, regenerated with `scripts/tenant-audit-census.mjs
--write`, with the seven hand-written prose figures brought along.

## Clause-② — re-derived from the delivered diff, not predicted

New exported symbols in the diff, and whether each is reachable from a
published entry. `plugin-auth`'s `exports` map has exactly two entries
(`.` to `dist/index.*`, `./rate-limit-storage` to
`dist/rate-limit-storage.*`); `admin-set-user-manager.ts` is
deliberately **not** re-exported from `src/index.ts` (0 matches).
Measured on the rebuilt `dist`, with two positive controls that ARE
re-exported, so the scan discriminates:

| symbol | `dist/index.d.ts` | `dist/rate-limit-storage.d.ts` |
| :--- | ---: | ---: |
| `runSetUserManager` | 0 | 0 |
| `MAX_MANAGER_CHAIN_DEPTH` | 0 | 0 |
| `SetUserManagerDeps` | 0 | 0 |
| `SetUserManagerEngine` | 0 | 0 |
| `SetUserManagerResult` | 0 | 0 |
| `SetUserManagerRefusalReason` | 0 | 0 |
| `runAdminBanUser` (control) | **2** | 0 |
| `SYS_USER_PROFILE_EDIT_FIELDS` (control) | **3** | 0 |

No already-published payload gains a key — the endpoint's response is a
new payload, not a new field on an old one. A new **route** is wire, and
wire compatibility is not the Clause-② floor. ⇒ **no**, changeset
`patch`.

`scripts/pm/check-widening-tells.mjs --declaration no` exits 0 but
reports **8 of 8 files NOT MEASURED** ("no declared surface covers it"),
so it is not a reading in either direction here and is not offered as
one.

⚠️ **Declared conflict, not silently resolved.** The ruling's own state
paragraph says "the endpoint carries `Clause-②: yes` and goes through
contract review". The PM claim declares `Clause-②: no`, explicitly as a
prediction to be re-derived from the delivered diff, and names the exact
flip condition — a re-export from `plugin-auth/src/index.ts`, or a new
key on a published payload. Neither holds, so both carriers now read
`no` and agree. If the seat that owns the vocabulary wants the tier
anyway on the strength of the ruling's sentence, that is its call to
make, and this note is here so it is made rather than inherited.

## Checks

Run on `614981107`, which is this branch merged with `origin/main` via
`scripts/pm/os-regen-merge.sh`, deps refreshed and every package rebuilt
(72/72 turbo tasks) afterwards.

| check | result |
| :--- | :--- |
| `pnpm --filter @objectstack/plugin-auth test` | **110 files, 2340
tests passed** |
| `pnpm --filter @objectstack/lint test` | **103 files, 3780 tests
passed** |
| `pnpm --filter @objectstack/plugin-auth typecheck` | exit 0 |
| `pnpm --filter @objectstack/lint typecheck` | exit 0 |
| derived gate families (`scripts/pm/dispatch-gates.mjs --commands`) |
**94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN** — reconciled with
`--ran` carrying an exit code per family, so the zero is derived rather
than claimed |
| `pnpm --filter @objectstack/spec check:generated` | all 15 generated
artifacts up to date after the merge |
| `pnpm exec eslint --no-inline-config` (narrowed) | 7 files, 0 errors,
0 warnings |

Three gate families were red on the first sweep and every one was a real
finding of this diff, fixed rather than baselined: `check:doc-authoring`
(the ledger `note` carried a tracker id — a runtime string no author can
resolve; moved to an adjacent comment), `check:where-matcher` (the new
test engine double read a WHERE combinator as a field name; it now
refuses loudly), and `check:tenant-audit-census` (the new engine write
moved the ratchet). Three more answered **exit 3, PREREQUISITE NOT MET**
— `check:skill-examples`, `check:dual-build-cjs-loads`,
`check:type-check-debt` — which is a statement about the tree and never
a pass; the closure they name was built and all three then exited 0.

**The eslint narrowing is a measurement, not a skipped run**, so its
three pieces of evidence are stated rather than implied: (i) the
population comes from `eslint.config.mjs`'s own `files` globs
(`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`), not from a guess; (ii) the
file count is read from `--format json` output, 7 entries; (iii) this
repo runs one `eslint.config.mjs` which **never enables type-aware
linting for any file** — no `parserOptions.project`, no typed rules,
stated in that file's own header with a recorded positive control — so
nothing in this diff can move the verdict on a file it does not touch.
The repo-wide `pnpm lint` is CI's run.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants